class Compile{
  constructor(el, vm, options){
    this.$el = document.querySelector(el);
    this.$vm = vm;
    /* 生命周期beforeCreated */
    options.beforeMount && options.beforeMount.call(this);
    /* 开始编译 */
    if (this.$el) {
      // 转换内部代码为fragment片段
      this.$fragment = this.domToFragment(this.$el);
      // 执行编译
      this.compile(this.$fragment);
      // 把编译完成的结果再放回去
      this.$el.appendChild(this.$fragment);
    }
    /* 生命周期beforeCreated */
    options.mounted && options.mounted.call(this);
  }
  domToFragment(el){
    const frag = document.createDocumentFragment();
    let child;
    while(child = el.firstChild){
      frag.appendChild(child);
    }
    return frag;
  }
  compile(frag){
    const childNodes = frag.childNodes;
    Array.from(childNodes).forEach(node => {
      if (node.nodeType === 1) { // 节点元素
        const attrs = node.attributes;
        Array.from(attrs).forEach(attr => {
          const name = attr.name;
          const value = attr.value;
          if (name.startsWith("k-")){ // 指令
            this.update(node, this.$vm, value, name.substring(2));
          } else if (name.startsWith("@")){ // 事件
            this.compileEvent(node, this.$vm, value, name.substring(1));
          }
        });
      } else if (node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent)) { // 文本，有插值的 {{xxx}} 这种
        this.compileText(node);
      }

      if(node.childNodes && node.childNodes.length > 0) {
        this.compile(node);
      }
    });
  }
  compileText(node) {
    const exp = RegExp.$1.split(' ').join('');
    console.log(exp);
    this.update(node, this.$vm, exp, "text");
  }
  update(node, vm, exp, dir){
    // 调用对应的更新函数,初始化
    const fn = this[dir + 'Updater'];
    fn && fn(node, vm, exp);
    // 依赖收集
    new Watcher(vm, exp, (value) => {
      fn && fn(node, vm, exp);
    });
  }
  textUpdater(node, vm, exp){
    node.textContent = vm[exp];
  }
  htmlUpdater(node, vm, exp){
    node.innerHTML = vm[exp];
  }
  modelUpdater(node, vm, exp){
    node.value = vm[exp];
    node.addEventListener("input", e => {
      vm[exp] = e.target.value;
    });
  }
  compileEvent(node, vm, exp, dir){
    let fn = vm[exp];
    if (fn && dir) {
      node.addEventListener(dir, fn.bind(vm));
    }
  }
}